package com.seeyon.apps.zjzd.Kit;

import com.seeyon.ctp.common.SystemEnvironment;
import com.seeyon.ctp.common.log.CtpLogFactory;
import net.sf.sevenzipjbinding.ExtractOperationResult;
import net.sf.sevenzipjbinding.IInArchive;
import net.sf.sevenzipjbinding.SevenZip;
import net.sf.sevenzipjbinding.impl.RandomAccessFileInStream;
import net.sf.sevenzipjbinding.simple.ISimpleInArchive;
import net.sf.sevenzipjbinding.simple.ISimpleInArchiveItem;
import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
import org.apache.commons.compress.archivers.sevenz.SevenZFile;
import org.apache.commons.logging.Log;

import java.io.*;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

public class FileUtilKit {

    private static final Log log = CtpLogFactory.getLog(FileUtilKit.class);

    /**
     * 解压各种格式（RAR、ZIP、7Z）的压缩文件到指定路径
     *
     * @param rarFile 压缩文件的路径和名称
     * @throws Exception 当解压过程中发生错误时抛出异常
     */
    public static void unRarZip7Z(String rarFile, String folderName) throws Exception {
        // 获取基础文件夹路径并确保路径使用正斜杠分隔
        String systemUrl = SystemEnvironment.getBaseFolder().replaceAll("\\\\", "/");
        // 定义上传文件夹路径
        String pathUrl = systemUrl + "/upload/customFile/upload/" + folderName + "/";
        File file = new File(pathUrl);
        if (!file.exists()) {
            file.mkdirs();
        }
        // 根据文件后缀名判断压缩文件的类型，并调用相应的解压方法
        if (rarFile.toLowerCase().endsWith(".rar")) {
            unRar(rarFile, pathUrl);
        } else if (rarFile.toLowerCase().endsWith(".zip")) {
            // 对于ZIP文件，指定使用GBK字符集解压，以避免中文文件名乱码问题
            Charset charset = Charset.forName("GBK");
            unZip(rarFile, pathUrl, charset);
        } else if (rarFile.toLowerCase().endsWith(".7z")) {
            un7z(rarFile, pathUrl);
        }
    }

    /**
     * 解压缩指定的ZIP文件到目标目录
     *
     * @param zipFile ZIP文件的路径
     * @param destDir 解压缩后的文件存放目录
     * @param charset 用于处理ZIP文件中条目名称的字符集
     * @throws IOException 如果读写文件时发生错误
     */
    private static void unZip(String zipFile, String destDir, Charset charset) throws IOException {
        // 创建ZipFile对象，指定ZIP文件路径和字符集
        ZipFile zip = new ZipFile(zipFile, charset);
        // 获取ZIP文件中的所有条目
        Enumeration<? extends ZipEntry> entries = zip.entries();
        // 遍历所有条目
        while (entries.hasMoreElements()) {
            // 获取当前条目
            ZipEntry entry = entries.nextElement();
            // 根据条目名称创建目标文件对象
            File file = new File(destDir, entry.getName());
            // 如果当前条目是目录，则创建对应的目录
            if (entry.isDirectory()) {
                file.mkdirs();
            } else {
                // 如果当前条目是文件，先创建其父目录，然后将条目内容写入到文件中
                file.getParentFile().mkdirs();
                try (InputStream is = zip.getInputStream(entry);
                     FileOutputStream fos = new FileOutputStream(file)) {
                    byte[] buffer = new byte[1024];
                    int length;
                    // 读取条目内容并写入到文件中
                    while ((length = is.read(buffer)) != -1) {
                        fos.write(buffer, 0, length);
                    }
                }

            }
        }
        // 关闭ZipFile对象
        zip.close();
    }

    /**
     * 复制给定文件到指定目录
     * 此方法旨在创建文件的副本，将原始文件内容复制到新位置，以便在不修改原始文件的情况下使用文件内容
     *
     * @param file 要复制的文件对象
     * @return 返回复制后的文件对象如果复制过程中发生错误，则返回null
     */
    public static File copyFile(File file) {
        File file_new = null;
        FileInputStream fileInputStream = null;
        FileOutputStream fileOutputStream = null;
        try {
            // 创建文件输入流以读取原始文件内容
            fileInputStream = new FileInputStream(file);

            // 获取基础文件夹路径并确保路径使用正斜杠分隔
            String systemUrl = SystemEnvironment.getBaseFolder().replaceAll("\\\\", "/");

            // 定义上传文件夹路径
            String pathUrl = systemUrl + "/upload/customFile/";

            // 创建上传文件夹对象
            File filePath = new File(pathUrl);

            // 检查上传文件夹是否存在，如果不存在则创建
            if (!filePath.exists()) {
                createFolder(pathUrl);
            }

            // 定义复制后文件的完整路径
            String pathUrl_new = pathUrl + file.getName();

            // 记录日志，记录文件临时存放路径
            log.info("附件临时存放路径:" + pathUrl_new);

            // 创建文件输出流以将文件内容写入新位置
            fileOutputStream = new FileOutputStream(pathUrl_new);

            // 创建缓冲区以存储读取的文件内容
            byte[] buffer = new byte[1024];
            int bytesRead;

            // 循环读取文件内容并写入新文件，直到读取完毕
            while ((bytesRead = fileInputStream.read(buffer)) != -1) {
                fileOutputStream.write(buffer, 0, bytesRead);
            }

            // 创建并返回复制后的文件对象
            file_new = new File(pathUrl_new);
        } catch (Exception e) {
            // 记录日志，记录文件复制失败的错误信息
            log.error("复制文件失败:", e);
        } finally {
            try {
                // 关闭文件输入流和输出流，释放系统资源
                if (fileInputStream != null) fileInputStream.close();
                if (fileOutputStream != null) fileOutputStream.close();
            } catch (IOException e) {
                // 记录日志，记录关闭文件流失败的错误信息
                log.error("关闭文件流失败:", e);
            }
        }
        return file_new;
    }

    /**
     * 解压7z格式的压缩包
     *
     * @param z7zFilePath 7z压缩包的文件路径
     * @param outPath     解压后的文件输出路径
     * @return 返回包含解压后文件名的映射
     */
    private static Map<String, String> un7z(String z7zFilePath, String outPath) {
        SevenZFile zIn = null;
        try {
            // 创建7z文件对象
            File file = new File(z7zFilePath);
            // 初始化7z文件读取对象
            zIn = new SevenZFile(file);
            SevenZArchiveEntry entry = null;
            File newFile = null;
            // 遍历7z压缩包中的每个条目
            while ((entry = zIn.getNextEntry()) != null) {
                //不是文件夹就进行解压
                if (!entry.isDirectory()) {
                    // 构建解压后的文件路径
                    newFile = new File(outPath, entry.getName());
                    // 如果文件不存在，则创建此文件的上层目录
                    if (!newFile.exists()) {
                        new File(newFile.getParent()).mkdirs();
                    }
                    // 使用文件输出流写入数据
                    OutputStream out = new FileOutputStream(newFile);
                    BufferedOutputStream bos = new BufferedOutputStream(out);
                    int len = -1;
                    byte[] buf = new byte[(int) entry.getSize()];
                    // 从7z文件中读取数据并写入目标文件
                    while ((len = zIn.read(buf)) != -1) {
                        bos.write(buf, 0, len);
                    }
                    bos.flush();
                    bos.close();
                    out.close();
                }
            }
        } catch (Exception e) {
            // 记录解压过程中出现的异常
            log.error("解压7z格式压缩包异常", e);
        } finally {
            try {
                // 确保7z文件流被正确关闭
                if (zIn != null) {
                    zIn.close();
                }
            } catch (IOException e) {
                // 记录关闭流过程中出现的异常
                log.error("解压7z格式压缩包关闭流异常", e);
            }
        }
        // 获取解压后的文件名列表并返回
        Map<String, String> resultMap = getFileNameList(outPath, "");
        return resultMap;
    }


    /**
     * 递归获取指定路径下的所有文件名及其路径
     *
     * @param path      文件或目录的路径
     * @param childName 用于记录路径中每个目录名称的字符串，用于生成唯一的键值
     * @return 包含文件名和路径的映射，键为文件名加上目录信息，值为文件的完整路径
     */
    private static Map<String, String> getFileNameList(String path, String childName) {
        // 初始化一个HashMap来存储文件名和路径的映射
        Map<String, String> files = new HashMap<>();

        // 创建一个File对象，代表传入的路径
        File file = new File(path);

        // 获取当前路径下所有文件和目录的名称
        String[] fileNameLists = file.list();

        // 获取当前路径下所有文件和目录的路径
        File[] filePathLists = file.listFiles();

        // 遍历所有文件和目录
        for (int i = 0; i < filePathLists.length; i++) {
            // 判断当前项是否为文件
            if (filePathLists[i].isFile()) {
                // 是文件，则将文件名和路径加入到映射中，键为文件名加上目录信息，值为文件的完整路径
                files.put(fileNameLists[i] + "==D==" + childName, path + File.separator + filePathLists[i].getName());
            } else {
                // 不是文件，则递归调用本方法，处理下一层目录，并将结果合并到当前的映射中
                files.putAll(getFileNameList(path + File.separator + filePathLists[i].getName(), childName + "&" + filePathLists[i].getName()));
            }
        }

        // 返回包含所有文件名和路径的映射
        return files;
    }

    /**
     * 创建指定路径的文件夹
     * 如果文件夹不存在，则创建文件夹；如果文件夹已存在，则直接返回
     *
     * @param path 想要创建的文件夹的路径
     * @return 返回创建或已存在的文件夹对象如果无法创建文件夹，则返回null
     */
    private static File createFolder(String path) {
        // 初始化文件对象
        File ret = null;
        File file = new File(path);

        // 检查文件夹是否已存在
        if (!file.exists()) {
            // 如果文件夹不存在，尝试创建文件夹
            if (file.mkdirs()) {
                // 成功创建文件夹后，将文件对象赋值给返回变量
                ret = file;
            }
        } else {
            // 如果文件夹已存在，直接将文件对象赋值给返回变量
            ret = file;
        }

        // 返回文件或null
        return ret;
    }

    /**
     * 将指定文件夹压缩为ZIP文件
     * 此方法递归地压缩指定文件夹下的所有文件和子文件夹，根据keepDirStructure参数决定是否保留目录结构
     *
     * @param srcDir           要压缩的文件夹路径，表示源目录
     * @param zipPathName      压缩文件的输出路径，包括文件名和扩展名
     * @param keepDirStructure 标志位，表示是否保留源文件夹的目录结构，true表示保留，false表示不保留
     */
    public static void toZip(String srcDir, String zipPathName, boolean keepDirStructure) {
        try (
                // 创建FileOutputStream对象，用于写入压缩文件
                FileOutputStream fileOutputStream = new FileOutputStream(new File(zipPathName));
                // 创建BufferedOutputStream对象，缓冲输出流，提高写入效率
                BufferedOutputStream out = new BufferedOutputStream(fileOutputStream);
                // 创建ZipOutputStream对象，用于创建ZIP格式的压缩文件
                ZipOutputStream zos = new ZipOutputStream(out);
        ) {
            // 根据源目录路径创建File对象
            File sourceFile = new File(srcDir);
            // 调用compress方法开始压缩文件，传入源文件、压缩输出流、源文件名和目录结构保留标志
            compress(sourceFile, zos, sourceFile.getName(), keepDirStructure);
        } catch (Exception e) {
            // 捕获异常，抛出运行时异常，包含异常信息
            throw new RuntimeException("压缩错误", e);
        }
    }

    /**
     * 压缩文件或文件夹
     *
     * @param sourceFile       要压缩的文件或文件夹
     * @param zos              ZipOutputStream对象，用于将数据写入压缩文件
     * @param name             当前文件或文件夹在压缩包中的名称
     * @param keepDirStructure 是否保持目录结构
     */
    private static void compress(File sourceFile, ZipOutputStream zos, String name, boolean keepDirStructure) {
        byte[] buf = new byte[2048];
        try {
            // 如果是文件，则直接压缩
            if (sourceFile.isFile()) {
                zos.putNextEntry(new ZipEntry(name));
                int len;
                FileInputStream in = new FileInputStream(sourceFile);
                while ((len = in.read(buf)) != -1) {
                    zos.write(buf, 0, len);
                }
                zos.closeEntry();
                in.close();
            } else {
                // 如果是文件夹，根据是否保持目录结构进行处理
                File[] listFiles = sourceFile.listFiles();
                if (listFiles == null || listFiles.length == 0) {
                    // 如果文件夹为空且需要保持目录结构，则在压缩文件中创建空的文件夹项
                    if (keepDirStructure) {
                        zos.putNextEntry(new ZipEntry(name + "/"));
                        zos.closeEntry();
                    }
                } else {
                    // 如果文件夹不为空，则递归压缩文件夹中的每个文件或文件夹
                    for (File file : listFiles) {
                        if (keepDirStructure) {
                            // 保持目录结构时，名称需要加上当前文件夹的名称
                            compress(file, zos, name + "/" + file.getName(), keepDirStructure);
                        } else {
                            compress(file, zos, file.getName(), keepDirStructure);
                        }
                    }
                }
            }
        } catch (Exception e) {
            // 异常处理，抛出运行时异常
            throw new RuntimeException("压缩错误", e);
        }
    }

    /**
     * 递归删除文件夹及其包含的所有文件和子文件夹
     * 此方法用于清理指定文件夹下的所有内容，包括文件和子文件夹
     * 应谨慎使用此方法，因为它会永久删除文件和文件夹
     *
     * @param folder 待删除内容的文件夹对象
     */
    public static void deleteFiles(File folder) {
        // 获取文件夹下的所有文件和子文件夹
        File[] files = folder.listFiles();
        // 检查文件数组是否非空
        if (files != null) {
            // 遍历文件和子文件夹
            for (File file : files) {
                // 判断当前文件是否为目录
                if (file.isDirectory()) {
                    // 如果是目录，则递归调用deleteFiles方法删除子文件夹及其内容
                    deleteFiles(file);
                } else {
                    // 如果是文件，则直接删除
                    file.delete();
                }
            }
            // 删除空文件夹
            folder.delete();
        }
    }

    /**
     * 解压RAR格式的压缩包到指定路径
     *
     * @param rarFilePath     压缩包的路径
     * @param outputDirectory 解压后文件输出的路径
     * @return 目前该方法返回空字符串，未来可能根据需要返回其他信息
     */
    private static void unRar(String rarFilePath, String outputDirectory) throws Exception {
        RandomAccessFile randomAccessFile = null;
        IInArchive inArchive = null;

        try {
            // 确保输出目录存在
            Path outputPath = Paths.get(outputDirectory);
            if (!Files.exists(outputPath)) {
                Files.createDirectories(outputPath);
            }

            // 打开RAR文件
            randomAccessFile = new RandomAccessFile(rarFilePath, "r");
            inArchive = SevenZip.openInArchive(null, new RandomAccessFileInStream(randomAccessFile));

            // 获取归档内容
            ISimpleInArchive simpleInArchive = inArchive.getSimpleInterface();

            // 遍历并提取所有项目
            for (ISimpleInArchiveItem item : simpleInArchive.getArchiveItems()) {
                if (!item.isFolder()) { // 跳过目录
                    String itemPath = item.getPath();
                    Path outputFilePath = outputPath.resolve(itemPath);

                    // 确保父目录存在
                    Files.createDirectories(outputFilePath.getParent());

                    // 提取文件
                    try (OutputStream outputStream = new BufferedOutputStream(
                            Files.newOutputStream(outputFilePath))) {
                        ExtractOperationResult result = item.extractSlow(data -> {
                            try {
                                outputStream.write(data);
                                return data.length; // 返回已处理的字节数
                            } catch (IOException e) {
                                throw new RuntimeException("Error writing extracted data", e);
                            }
                        });

                    }
                }
            }
        } catch (Exception e) {
            throw new Exception("Error extracting RAR file", e);
        } finally {
            try {
                if (inArchive != null) {
                    inArchive.close();
                }
                if (randomAccessFile != null) {
                    randomAccessFile.close();
                }
            } catch (Exception e) {
                log.error("Error closing resources: ", e);
            }
        }
    }
}
